home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Peter Lewis / PNL Libraries / NeoTextBox.p < prev    next >
Encoding:
Text File  |  1994-10-27  |  11.4 KB  |  324 lines  |  [TEXT/PJMM]

  1. unit NeoTextBox;
  2.  
  3. { Written in C by Bryan K. Ressler (Beaker), from Develop issue 9 }
  4.  
  5. interface
  6.  
  7.     const
  8.         ntbJustFull = 128;
  9.  
  10.     procedure NeoTextBox (theText: Ptr; textLen: longInt; wrapBox: Rect; align: integer; lhCode: integer; var endY: integer; var lhUsed: integer; var lineCount: integer);
  11.  
  12. implementation
  13.  
  14.     uses
  15.         Script, FixMath, QLowLevel, MyMathUtils;
  16.  
  17.     const
  18.         kReturnChar = 13;
  19.  
  20. {}
  21. {* NTBLineHeight - figures line height}
  22. {*}
  23. {* Input:    theText        the entire text that was given to the NeoTextBox call}
  24. {*            textLen        the length in bytes of the text}
  25. {*            lhCode        the line height code that was passed to NeoTextBox}
  26. {*            startY        VAR - we return the starting vertical pen location here}
  27. {*}
  28. {* Output:    returns the line height to use}
  29.     function NTBLineHeight (theText: Ptr; textLen: longInt; wrapBox: rect; lhCode: integer; var startY: integer): integer;
  30.         const
  31.             kTrueTypeTrap = $54;
  32.             kUnimplTrap = $9f;
  33.  
  34.         var
  35.             asc, desc: integer;    { Used in the OutlineMetrics calls }
  36.             fInfo: FontInfo;        { The old-style font information record }
  37.             frac: Point;        { The fraction for the TrueType calls }
  38.             lineHeight: integer;    { The return value }
  39.             hasTrueType: boolean;
  40.             err: OSErr;
  41.     begin
  42.  
  43.         GetFontInfo(fInfo);
  44.         if lhCode < 0 then begin
  45.  
  46. {            If the user has specified variable-height lines, we need to try}
  47. {            to determine the tallest ascent in the given text.  We can only}
  48. {            really do this if the font is a TrueType font.  Otherwise, we}
  49. {            punt and use old-fashioned GetFontInfo numbers.}
  50.  
  51.             frac.h := 1;
  52.             frac.v := 1;
  53.             hasTrueType := NGetTrapAddress(kTrueTypeTrap, ToolTrap) <> NGetTrapAddress(kUnimplTrap, ToolTrap);
  54.             if hasTrueType & IsOutline(frac, frac) then begin
  55.  
  56. {                At this point we know the current font is a TrueType font, so}
  57. {                we do an OutlineMetrics call with our full text.  It will put}
  58. {                the tallest character ascent into asc, and the deepest descent}
  59. {                into desc.  Then we choose between whichever's most between}
  60. {                the old-style ascent/descent and the numbers we get from}
  61. {                the OutlineMetrics call.}
  62. {            }
  63.  
  64.                 err := OutlineMetrics(textLen, theText, frac, frac, asc, desc, nil, nil, nil);
  65.  
  66.             end
  67.             else begin
  68.  
  69. {                At this point we know the current font isn't TrueType, so we}
  70. {                just use the old way of calculating line height.}
  71.                 err := -1;
  72.             end;
  73.             if err <> noErr then begin
  74.                 asc := 0;
  75.                 desc := 0;
  76.             end;
  77.             lineHeight := Max(fInfo.ascent, asc) + Max(fInfo.descent, -desc) + fInfo.leading;
  78.             startY := wrapBox.top + Max(fInfo.ascent, asc) + fInfo.leading;
  79.         end
  80.         else if lhCode = 0 then begin
  81.  
  82.         {}
  83. {            If the user has specified "default" line height, he just wants us}
  84. {            to get the line height from the FontInfo record.}
  85. {        }
  86.  
  87.             lineHeight := fInfo.ascent + fInfo.descent + fInfo.leading;
  88.             startY := wrapBox.top + fInfo.ascent + fInfo.leading;
  89.  
  90.         end
  91.         else begin
  92.  
  93.         { If the user has provided a specific line height, we just trust}
  94. {            them.  We can't really generate too good of a starting vertical}
  95. {            coordinate, but we munge one together anyway.}
  96. {        }
  97.  
  98.             lineHeight := lhCode;
  99.             startY := wrapBox.top + lhCode + fInfo.leading;
  100.  
  101.         end;
  102.  
  103.         NTBLineHeight := lineHeight;
  104.     end;
  105.  
  106. {}
  107. {* NTBDraw - draws a line with appropriate justification}
  108. {*}
  109. {* Input:    breakCode    the break code that was returned from StyledLineBreak}
  110. {*            lineStart    pointer to the beginning of the text for the current line}
  111. {*            lineBytes    the length in bytes of the the text for this line}
  112. {*            wrapBox        the box within which we're wrapping}
  113. {*            align        the text alignment as specified by the user}
  114. {*            curY        our current vertical pen coordinate}
  115. {*            boxWidth    the width of wrapBox (since NeoTextBox already calculated it)}
  116. {*}
  117. {* Output:    none (draws on the screen)}
  118. {}
  119.     procedure NTBDraw (breakCode: StyledLineBreakCode; lineStart: ptr; lineBytes: longInt; wrapBox: Rect; align: integer; curY: integer; boxWidth: integer);
  120.         var
  121.             blackLen: longInt;    { Length of non-white characters }
  122.             slop: integer;        { Number of pixels of slop for full just }
  123.     begin
  124.  
  125.     {}
  126. {        The first thing we do here is determine the length of the "black" part}
  127. {        of the current line.  This excludes spaces, carriage returns, and other}
  128. {        white stuff depending on the language.  How do we know what to elim-}
  129. {        inate?  We DON'T!  So we ask our friend the Script Manager to do it}
  130. {        for us.  VisibleLength returns the number of bytes that we should use}
  131. {        for pixel width calculations.}
  132. {    }
  133.  
  134.         blackLen := VisibleLength(lineStart, lineBytes);
  135.  
  136.         if align = ntbJustFull then begin
  137.  
  138.         {}
  139. {            For full justification, we need to calculate the "slop" space on}
  140. {            the line that's not filled up by the text.  Then we move to the}
  141. {            margin and get ready to draw BUT WAIT!  If this is the last line of}
  142. {            a paragraph, we need to draw it with whatever the system justifi-}
  143. {            cation is.  So if the break code indicates we're at the end of the}
  144. {            text, or we can find a carriage return there ourselves, we just}
  145. {            change the text alignment to the current default system text align-}
  146. {            ment and fall through without drawing.  If it's just another line}
  147. {            within a paragraph, we use the handy Script Manager routine DrawJust}
  148. {            to draw it justified.  In languages like Arabic, full justification}
  149. {            is performed differently than just spacing out the words.  DrawJust}
  150. {            handles cases like these correctly.}
  151. {            }
  152. {            Note that when we go looking for the carriage return at the end of}
  153. {            the line, it's okay to simply index to the last byte of the string.}
  154. {            This is because the carriage return character, 0x0d, is in the}
  155. {            control-code range, which is defined to never be the low byte of a}
  156. {            two byte character.}
  157. {        }
  158.  
  159.             slop := boxWidth - TextWidth(lineStart, 0, blackLen);
  160.             MoveTo(wrapBox.left, curY);
  161.             if (breakCode = smBreakOverflow) | (AddPtrLong(lineStart, lineBytes - 1)^ = kReturnChar) then begin
  162.                 align := GetSysJust;
  163.             end
  164.             else begin
  165.                 DrawJust(lineStart, blackLen, slop);
  166.             end;
  167.         end;
  168.  
  169.     {}
  170. {        For the rest of the text alignments (left, center, and right), we just}
  171. {        move the pen to the right place and draw the text with DrawText.  Note}
  172. {        that the alignments that could have come into the NeoTextBox call have}
  173. {        been resoved down to one of the four constants teForceLeft, teJustRight}
  174. {        teJustCenter, or ntbJustFull.}
  175. {    }
  176.  
  177.         case align of
  178.             teForceLeft, teJustLeft: 
  179.                 MoveTo(wrapBox.left, curY);
  180.             teJustRight: 
  181.                 MoveTo(wrapBox.right - TextWidth(lineStart, 0, blackLen), curY);
  182.             teJustCenter: 
  183.                 MoveTo(wrapBox.left + (boxWidth - TextWidth(lineStart, 0, blackLen)) div 2, curY);
  184.             otherwise
  185.                 ;
  186.         end;
  187.         if align <> ntbJustFull then begin
  188.             DrawText(lineStart, 0, lineBytes);
  189.         end;
  190.     end;
  191.  
  192. {}
  193. {* NeoTextBox - word-wraps text inside a given box}
  194. {*}
  195. {* Input:    theText        the text we need to wrap}
  196. {*            textLen        the length in bytes of the text}
  197. {*            wrapBox        the box within which we're wrapping}
  198. {*            align        the text alignment}
  199. {*                            teForceLeft, teFlushLeft    left justified}
  200. {*                            teJustCenter, teCenter        center justified}
  201. {*                            teJustRight, teFlushRight    right justified}
  202. {*                            ntbJustFull                    full justified}
  203. {*                            teJustLeft, teFlushDefault    system justified}
  204. {*            lhCode        the line height code that was passed to NeoTextBox}
  205. {*                            < 0        variable - based on tallest character}
  206. {*                            0        default - based on GetFontInfo}
  207. {*                            > 0        fixed - use lhCode as the line height}
  208. {*            endY        VAR - if non-nil, the vertical coord of the last line}
  209. {*            lhUsed        VAR - if non-nil, the line height used to draw the text}
  210. {*}
  211. {* Output:    returns the number of line drawn total (even if they drew outside of}
  212. {*            the boundries of wrapBox)}
  213. {}
  214.     procedure NeoTextBox (theText: Ptr; textLen: longInt; wrapBox: Rect; align: integer; lhCode: integer; var endY: integer; var lhUsed: integer; var lineCount: integer);
  215.         var
  216.             oldClip: RgnHandle;        { Saved clipping region }
  217.             breakCode: StyledLineBreakCode;        { Returned code from StyledLineBreak }
  218.             fixedMax: Fixed;        { boxWidth converted to fixed point }
  219.             wrapWid: Fixed;        { Width to wrap to }
  220.             boxWidth: integer;        { Width of the wrapBox }
  221.             lineBytes: longInt;        { Number of bytes in one line }
  222.             lineHeight: integer;        { Calculated line height }
  223.             curY: integer;            { Current vertical pen location }
  224.             textLeft: longInt;        { Pointer to remaining bytes of text }
  225.             lineStart: Ptr;        { Pointer to beginning of a line }
  226.             textEnd: Ptr;        { Pointer to the end of input text }
  227.     begin
  228.     {}
  229. {        First, we save the old clipping region and clip to wrapBox.  Then, figure}
  230. {        the width of wrapBox, and make a fixed point version of it.  Also, resolve}
  231. {        the text alignment teFlushDefault to be the default system text alignment.}
  232. {    }
  233.         oldClip := NewRgn;
  234.         GetClip(oldClip);
  235.         ClipRect(wrapBox);
  236.         boxWidth := wrapBox.right - wrapBox.left;
  237.         fixedMax := Long2Fix(boxWidth);
  238.         if align = teFlushDefault then begin
  239.             align := GetSysJust;
  240.         end;
  241.  
  242.     {}
  243. {        Now we call NTBLineHeight to calculate the appropriate line height.  It}
  244. {        also figures our starting vertical pen location in curY based on the}
  245. {        line height and other metric parameters.  Clear lineCount, set}
  246. {        lineStart to point to the beginning of the text, calculate textEnd for}
  247. {        comparison to know when we're done, and preset textLeft to be all the}
  248. {        text.}
  249. {    }
  250.  
  251.         lineHeight := NTBLineHeight(theText, textLen, wrapBox, lhCode, curY);
  252.         lineCount := 0;
  253.         lineStart := theText;
  254.         textEnd := AddPtrLong(theText, textLen);
  255.         textLeft := textLen;
  256.  
  257.     {}
  258. {        This is the main wrap-and-draw loop.  I bet you never thought wrapping}
  259. {        text could be so easy...}
  260. {    }
  261.  
  262.         while textLeft > 0 do begin
  263.  
  264.         {}
  265. {            Every line, we have to preset lineBytes to something non-zero.}
  266. {            This tells StyledLineBreak that we're drawing the first format}
  267. {            run on the line (of course, for us, there's only ONE format run}
  268. {            total).  Also preset wrapWid.  StyledLineBreak will always modify}
  269. {            lineBytes (to tell you how many bytes are on this line), and will}
  270. {            modify wrapWid, so we have to reset them each line.}
  271. {        }
  272.  
  273.             lineBytes := 1;
  274.             wrapWid := fixedMax;
  275.  
  276.             breakCode := StyledLineBreak(lineStart, textLeft, 0, textLeft, 0, wrapWid, lineBytes);
  277.  
  278.         {}
  279. {            Now that the Script Manager has done all the really hard work for}
  280. {            us, we draw the line.  We already knew lineStart, StyledLineBreak}
  281. {            gave us lineBytes, which we pass to NTBDraw.  It'll handle the}
  282. {            different text alignments itself.}
  283. {        }
  284.  
  285.             NTBDraw(breakCode, lineStart, lineBytes, wrapBox, align, curY, boxWidth);
  286.  
  287.         {}
  288. {            Now we advance our vertical position down by the height of one}
  289. {            line, advance lineStart by the number of bytes we just drew,}
  290. {            calculate a new textLeft, and increment our line count.}
  291. {        }
  292.  
  293.             curY := curY + lineHeight;
  294.             lineStart := AddPtrLong(lineStart, lineBytes);
  295.             textLeft := textLeft - lineBytes;
  296.             lineCount := lineCount + 1;
  297.  
  298.         end;
  299.  
  300.     {}
  301. {        Well that was a job well done.  Let's return some useful values, too.}
  302. {        If the user gave pointers for endY and lhUsed, we stuff our ending}
  303. {        vertical coordinate and the line height we calculated into those,}
  304. {        respectively.  These allow the guy to put something after the wrapped}
  305. {        text (or at least know the right place to put it).}
  306. {    }
  307.  
  308.         endY := curY - lineHeight;
  309.         lhUsed := lineHeight;
  310.  
  311.     {}
  312. {        Finally, restore the clipping region, dispose of the region, and}
  313. {        return the TOTAL number of lines drawn (note that we didn't stop}
  314. {        drawing when curY advanced past wrapBox->bottom.  This way, the user}
  315. {        could tell that the text overflowed wrapBox.  If you want to know how}
  316. {        many lines fit, divide wrapBox by lhUsed.  This way, you get the best}
  317. {        of both worlds.}
  318. {    }
  319.  
  320.         SetClip(oldClip);
  321.         DisposeRgn(oldClip);
  322.     end;
  323.  
  324. end.